package xu.jiang.report.dealer.layer;

import org.apache.poi.ss.usermodel.Sheet;
import xu.jiang.report.bean.LayerOption;
import xu.jiang.report.bean.RowArea;
import xu.jiang.report.util.report.POIUtil;

import java.io.IOException;
import java.util.List;

/**
 * @apiNote 打印层，专用与打印的层
 * 提供很多适配于打印的分页、固定纸张高度的设置
 * @printRule 如果小于等于2页，则每页相同，打印表头表尾；
 * 如果大于等于三页，则只有第一页和最后一页打印表头表尾，中间页只打印表头不打印表尾
 */
public class PrintLayer extends FixedDataHeightLayer {

    private final LayerOption layerOption;

    public PrintLayer(LayerBaseParam layerBaseParam, LayerOption layerOption) throws IOException {
        super(layerBaseParam, layerOption.getDataHeightInHeadPage());
        this.layerOption = layerOption;
        this.initParamsUnSafely();
    }

    /**
     * 不安全的参数初始化
     */
    private void initParamsUnSafely(){
        if(this.layerOption.getDataHeightInHeadPage()==null){
            return;
        }
        if(this.layerOption.getDataHeightInTailPage()==null){
            this.layerOption.setDataHeightInTailPage(this.layerOption.getDataHeightInHeadPage());
        }
        if(this.layerOption.getDataHeightInMiddlePage() ==null){
            this.layerOption.setDataHeightInMiddlePage(this.layerOption.getDataHeightInHeadPage());
        }
    }

    /**
     * ###分页规则如下：
     * 我们假设 head 代表第一页的数据行数；tail 代表最后一页的数据行数； mid 代表中间页的数据行数
     * 则 存在以下结论：
     * a. head=tail
     * b. mid>tail
     * c. mid=tail+m（m>=0）
     * 这里面 pageSize 的计算是复杂的：
     * 1 当 dataSize <= head 时，只有第一页
     * 2 当 head+tail >= dataSize > head 时，只有两页，且是首页和尾页
     * 3 当 dataSize > head+tail 时，存在中间页，这里需要细分 (N 为自然数，代表中间页的页数)：
     * 3.1 head + mid*N >= dataSize > head +mid*(N-1)+ tail 时，存在三页，但三页都需要表头表尾（客户要求）
     * 这也意味着：第一页行数为 head，中间页行数为 mid*(N-1)+tail;尾页行数 dataSize-head-mid*(N-1)-tail
     * 3.2 head + mid*N +tail >= dataSize > head + mid*N 时，
     * head 为第一页，mid*N 为中间页，而 dataSize-head-mid*N 为尾页。
     * <p>
     * -------------------------------------------------
     * ###分页时写入数据规则如下：
     * sheet 内部分页
     * 由于，每次都要复制一份表格，所以需要提前存储起来。
     * 这里的存储表格逻辑如下：
     * (1) 初始状态下：当前表格为【模版】
     * (2) 如果当前页是最后一页，则直接使用【模版】，#打印结束。
     * (3) 如果不是最后一页，操作如下：首先，需要向下复制表格。
     * 将【模版】变为当前页的数据表格用来放置数据，将向下的复制表格变为【模版】
     * (4) 递归重复 (2)、(3),直至分页结束。
     */
    @Override
    public int writeData(int appendRowNum, Sheet sheet, MySQLData data) {
        if (this.layerOption.getDataHeightInHeadPage() <= 0) {
            throw new IllegalArgumentException("the param 'fixedDataHeight' must be more than 0");
        }
        List<Object[]> olist = data.pureData;
        //数据总条数
        int dataSize = olist.size();


        //如果只需要一页，就不用分页了。
        if (dataSize <= this.layerOption.getDataHeightInHeadPage()) {
            return super.writeData(appendRowNum, sheet, data);
        }

        //当前模版的起始行
        RowArea currentModelTable = new RowArea(this.layerOption.getStartRowIndexOfModel(), this.layerOption.getEndRowIndexOfModel(),this.layerOption.getMarginHeight());

        //计算当前数据写入的行号
        int currentIndexRow = appendRowNum + this.getIndex();

        /**
         * 总页数小于等于2页时
         */
        if (dataSize <= (this.layerOption.getDataHeightInHeadPage() + this.layerOption.getDataHeightInTailPage())) {

            //向下复制表格
            RowArea newModelTable = POIUtil.copyModelTable(sheet, currentModelTable);

            //尾页：第一页
            this.writeDataInHeadPage(currentIndexRow, sheet, data);

            //移动行指针
            currentIndexRow += newModelTable.getOffsetInTables();
            //尾页：尾页
            int tail_dataStart = this.layerOption.getDataHeightInHeadPage();
            this.writeDataInTailPage(currentIndexRow, sheet, data, tail_dataStart);

            return 0;
        }

        /**
         * 进入这里，说明超过两页，但最后一个中间页可能是【尾页的打印效果（带表头表尾）】
         * 如上所致，中间页分为两种情况。这也导致计算 pageSize 有些复杂
         * 计算总页数 pageSize，采用向上取整
         * 计算方法：
         * 1 s = (dataSize-head)/mid; y = (dataSize-head)%mid
         * 2 if y == 0 then 最后一个中间页应该一分为二，都要带头带尾渲染。 即 pageSize = 1+s+1（注意把第一页加上去）
         *   if 0< y <= tail then pageSize = 1+s+1
         *   else {
         *      tail< y < mid then pageSize = 1+s+(int)Math.ceil(y*1.0/tail)
         *      这意味着，虽然 mid<y 但可能 mid很大，tail很小。即签字区域很长，远大于模版表格内的数据行。
         *      即当 tail < mid< tail*N 时 pageSize = s+1+N 这已经目不符合前交工资料 A4 纸的情况。有点扯了。
         *   }
         *
         * 上述逻辑清晰，但实现复杂。
         * 故采用如下方式：
         *  y==0 时，pageSize = 1次（第一次）+(s-1) 剩下的数据跳出循环单独处理。
         *  y>0 时 pageSize=1+s  剩下的数据跳出循环单独处理。
         */
        //商
        int shang = (dataSize - this.layerOption.getDataHeightInHeadPage()) / this.layerOption.getDataHeightInMiddlePage();
        //余数
        int yushu = (dataSize - this.layerOption.getDataHeightInHeadPage()) % this.layerOption.getDataHeightInMiddlePage();

        int pageSize = shang + 1;
        if (yushu <= 0) {
            pageSize--;
        }

        //分页，注意 pageSize 里面必定包含了一个headPage，所以在计算中间页数时需要减一
        int currentPage = 1;
        for (; currentPage <= pageSize; currentPage++) {

            //向下复制表格
            RowArea newModelTable = POIUtil.copyModelTable(sheet, currentModelTable);

            if (currentPage == 1) {

                this.writeDataInHeadPage(currentIndexRow, sheet, data);

            } else {
                //中间页
                this.writeDataInMiddlePage(currentIndexRow, sheet, data, currentPage, currentModelTable);
            }

            //模版位置更新，,将复制后的 table 作为新【模版】
            currentModelTable = newModelTable;
            //当前行号
            currentIndexRow += newModelTable.getOffsetInTables();

        }

        /**
         * 进入这里，说明进入了剩余部分:剩下部分 <= mid
         */
        int newYushu = dataSize - this.layerOption.getDataHeightInHeadPage() - this.layerOption.getDataHeightInMiddlePage() * (pageSize-1);
        //只需要一页
        if (newYushu <= this.layerOption.getDataHeightInTailPage()) {
            //进入这里，说明 剩余的 <= tail,只需要一页,
            int dataStartIndex = this.layerOption.getDataHeightInHeadPage() + this.layerOption.getDataHeightInMiddlePage() * (pageSize-1);
            this.writeDataInTailPage(currentIndexRow, sheet, data, dataStartIndex);

            return 0;
        }

        //至少需要两页 ，包含都表头和表尾
        //计算需要的尾页数量
        int pageSizeOfTail = (int)Math.ceil(newYushu*1.0/this.layerOption.getDataHeightInTailPage());

        //尾部 第1个表格
        int dataStartIndex = this.layerOption.getDataHeightInHeadPage() + this.layerOption.getDataHeightInMiddlePage() * (pageSize-1);

        for(int i=1;i<=pageSizeOfTail;i++){
            if(i==pageSizeOfTail){ //最后一页不用复制模版
                this.writeDataInTailPage(currentIndexRow, sheet, data, dataStartIndex);
                continue;
            }
            //向下复制表格
            RowArea newModelTable = POIUtil.copyModelTable(sheet, currentModelTable);

            this.writeDataInTailPage(currentIndexRow, sheet, data, dataStartIndex);
            //下个表格的行号移动
            currentIndexRow += newModelTable.getOffsetInTables();
            //下个表格的数据起始位置
            dataStartIndex = dataStartIndex + this.layerOption.getDataHeightInTailPage();
        }

        return 0;
    }


    /**
     * 输出第一页
     *
     * @param currentIndexRow
     * @param sheet
     * @param data
     */
    private void writeDataInHeadPage(int currentIndexRow, Sheet sheet, MySQLData data) {
        //计算数据范围  0 4   5ge
        int dataStart = 0;
        int dateEnd = 0 + this.layerOption.getDataHeightInHeadPage() - 1;

        this.writeDataByStartRowIndex(currentIndexRow, sheet, data, dataStart, dateEnd);
    }

    /**
     * 输出中间页
     *
     * @param currentIndexRow
     * @param sheet
     * @param data
     * @param currentPage
     */
    private void writeDataInMiddlePage(int currentIndexRow, Sheet sheet, MySQLData data
            , int currentPage, RowArea currentModelTable) {
        if (currentPage < 2) {
            throw new IllegalArgumentException("currentPage should more than or equals 2");

        }

        //清除底部的签字区域
        int start4Clearing = currentIndexRow + this.layerOption.getDataHeightInHeadPage();
        int end4Clearing = currentModelTable.getEndRowIndex();
        POIUtil.removeRows(sheet, start4Clearing, end4Clearing);

        //计算数据范围
        int dataStart = this.layerOption.getDataHeightInHeadPage() + (currentPage - 2) * this.layerOption.getDataHeightInMiddlePage();
        int dateEnd = dataStart + this.layerOption.getDataHeightInMiddlePage() - 1;

        this.writeDataByStartRowIndex(currentIndexRow, sheet, data, dataStart, dateEnd);

    }

    /**
     * 输出最后一页
     *
     * @param currentIndexRow
     * @param sheet
     * @param data
     */
    private void writeDataInTailPage(int currentIndexRow, Sheet sheet, MySQLData data, int dataStart) {
        //计算数据范围  0 4   5ge
        int dateEnd = dataStart + this.layerOption.getDataHeightInTailPage() - 1;

        //检查下剩下的数据中是否充足
        if(dateEnd>=data.pureData.size()){
            dateEnd = data.pureData.size()-1;
        }

        this.writeDataByStartRowIndex(currentIndexRow, sheet, data, dataStart, dateEnd);
    }


}
